/********************************************************************************
 * Copyright (c)2016-2020, ShenZhen WanJiaAn Technology CO.,LTD.
 * All Rights Reserved.
 *
 * File Name     : NtpClient.c
 * Author        : joki.zhu
 * Date          : 2020-04-17
 *
 * Record        :
 * 1.Date        : 2020-04-17
 *   Author      : joki.zhu
 *   Modification: Created file
 ********************************************************************************/

#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <time.h>

#ifdef __HuaweiLite__
#include "netif/ifaddrs.h"
#include "lwip/sockets.h"
#include "lwip/api.h"
#include "lwip/sys.h"
#include "lwip/igmp.h"
#include "lwip/inet.h"
#include "lwip/tcp.h"
#include "lwip/raw.h"
#include "lwip/udp.h"
#include "lwip/tcpip.h"
#include "lwip/pbuf.h"
#include "lwip/netif.h"
#include "lwip/ip_addr.h"
#include "lwip/dhcp.h"
#include "lwip/filter.h"
#endif /* __HuaweiLite__ */

#include "System.h"

//#define NTP_CLIENT_DEBUG

#define NTP_SERVER_PORT 	(123)
#define JAN_1970     		0x83aa7e80 /* seconds (1970 - 1900) */
#define NTPFRAC(x) (4294 * (x) + ((1981 * (x))>>11))
#define USEC(x) (((x) >> 12) - 759 * ((((x) >> 10) + 32768) >> 16))
#define LI 0
#define VN 3
#define MODE 3
#define STRATUM 0
#define POLL 4 
#define PREC -6

struct NtpTime 
{
    unsigned int coarse;
    unsigned int fine;
};

static char gs_bNtpOk = 0;

static int print_ntp_time(char *server, struct timeval tvTime)
{
    time_t time = 0;
    struct tm tmTime = {0};
    char dateStr[40] = "";

    time = tvTime.tv_sec;
    localtime_r(&time, &tmTime); /* utc time to localtime with timezone */
    strftime(dateStr, 40, "%Y-%m-%d %H:%M:%S", &tmTime);
    printf("ntpserver: %s time: %s\n", server, dateStr);

    return 0;
}

static int set_system_time(struct timeval tvTime)
{
    time_t time = 0;
    struct tm *ptmTime = NULL;
    struct tm tmTime = {0};

    time = tvTime.tv_sec;
    localtime_r(&time, &tmTime); /* utc time to localtime with timezone */

    printf("ntp sync time: %04d-%02d-%02d %02d:%02d:%02d\n", (int)tmTime.tm_year+1900, (int)tmTime.tm_mon+1, (int)tmTime.tm_mday,
                                                    (int)tmTime.tm_hour, (int)tmTime.tm_min, (int)tmTime.tm_sec);									

    SYSTEM_TIME stSysTime;
    memset(&stSysTime, 0, sizeof(stSysTime));
    stSysTime.year = (int)tmTime.tm_year+1900;
    stSysTime.month = (int)tmTime.tm_mon+1;
    stSysTime.day = (int)tmTime.tm_mday;
    stSysTime.hour = (int)tmTime.tm_hour;
    stSysTime.minute = (int)tmTime.tm_min;
    stSysTime.second = (int)tmTime.tm_sec;
    SystemSetCurrentTime(&stSysTime);
    
    return 0;
}

static int domain_to_ip(char *inAddr, char *outIp)
{
    struct addrinfo * res;
    struct addrinfo hints;
    int succ = 0;
    struct sockaddr_in dst_addr;

    /* Judge whether the input address is domain name or IP */
    memset(&dst_addr, 0, sizeof(dst_addr));
    if ((dst_addr.sin_addr.s_addr = inet_addr(inAddr)) != INADDR_NONE)  {
        sprintf(outIp, "%s", inAddr);
        return 0;
    }
	
    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_family = AF_INET; /* Allow IPv4 */
    hints.ai_flags = AI_PASSIVE; /* For wildcard IP address */
    hints.ai_protocol = 0; /* Any protocol */
    hints.ai_socktype = SOCK_STREAM;
    succ = getaddrinfo(inAddr, NULL, &hints, &res);
    if (succ != 0) {
#ifdef NTP_CLIENT_DEBUG
        printf("can't get address info! error code = %d\n", succ);
#endif /* NTP_CLIENT_DEBUG */
        return -1;
	} else {
        if (res->ai_addr->sa_family == AF_INET)
        {
            if (outIp != NULL) {
                struct sockaddr_in* pServAddr = (struct sockaddr_in *)res->ai_addr;
                inet_ntop(AF_INET, &(pServAddr->sin_addr), outIp, INET_ADDRSTRLEN);
            }
        }
    }

    freeaddrinfo(res);

    return 0;
}

static int ntp_send_request(int sockfd)
{
    unsigned int data[12] = {0};
    struct timeval now;

    if (sizeof(data) != 48)  {
        printf("ntp requeset size is error!\n");
        return -1;
    }

    memset((char*)data, 0, sizeof(data));
    /* 构造协议头信息 */
    data[0] = htonl((LI << 30) | (VN << 27) | (MODE << 24) | (STRATUM << 16) | (POLL << 8) | (PREC & 0xff));
    data[2] = htonl(1<<16);
    gettimeofday(&now, NULL);
    data[10] = htonl(now.tv_sec + JAN_1970);/* 构造传输时间 */
    data[11] = htonl(NTPFRAC(now.tv_usec));
    send(sockfd, data, 48, 0);

    return 0;	
}

static int ntp_parse_time(unsigned int *pData, struct timeval *ptvTime)
{
    struct NtpTime trantime;

#define DataOffset(i) ntohl(((unsigned int *)pData)[i])
    trantime.coarse = DataOffset(10);
    trantime.fine   = DataOffset(11);
#undef DataOffset

    ptvTime->tv_sec 	= trantime.coarse - JAN_1970;
    ptvTime->tv_usec 	= USEC(trantime.fine);

    return 0;	
}

static int ntp_sync_time(char *serverAddr, struct timeval *ptvTime)
{
    if (serverAddr == NULL) {
        printf("serverAddr is invalid!\n");
        return -1;
    }

    int ret = 0;
    int sockfd = -1;
    char host[16] = "";
    struct sockaddr_in socket_addr = {0};
    struct timeval tvTimeout;
    fd_set readfds;
    unsigned int dataBuf[12] = {0};
    int retryCnt = 0;
    int retryMaxNum = 10;
	
    ret = domain_to_ip(serverAddr, host);
    if (ret < 0) {
#ifdef NTP_CLIENT_DEBUG
        printf("domain_to_ip %s failed\n", serverAddr);
#endif /* NTP_CLIENT_DEBUG */
        return -1;
    }

    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0) {
        printf("ntp socket create failed\n");
        return -1;
    }

    socket_addr.sin_family = AF_INET;
    socket_addr.sin_port = htons(NTP_SERVER_PORT);
    inet_aton(host, &(socket_addr.sin_addr));

    if (connect(sockfd, (struct sockaddr *)(&socket_addr), sizeof(struct sockaddr)) < 0) {
        printf("ntp connect %s failed\n", serverAddr);
        ret = -1;
        goto close_fd;
    }

    /* sendt ntp requeset pkt */
    ntp_send_request(sockfd);

    /* recv ntp data */
    while (retryCnt < retryMaxNum)
    {
        FD_ZERO(&readfds);
        FD_SET(sockfd, &readfds);

        tvTimeout.tv_sec = 0;
        tvTimeout.tv_usec = 100000;
        ret = select(sockfd + 1, &readfds, NULL, NULL, &tvTimeout);
        if (0 == ret) {
            retryCnt++;
            printf("select ntp timeout!\n");
            ntp_send_request(sockfd); //send again
            usleep(100 * 1000);
            continue;
        } else if (ret < 0) {
            printf("select ntp failed\n");
            ret = -1;
            goto close_fd;
        }

        if (FD_ISSET(sockfd, &readfds))
        {
            memset(dataBuf, 0, sizeof(dataBuf));
            if (recv(sockfd, dataBuf, sizeof(dataBuf), 0) <= 0) {
                printf("recv ntp failed\n");
                ret = -1;
                goto close_fd;
            }

            ntp_parse_time(dataBuf, ptvTime);
            //printf("sec = %ld usec = %ld\n", ptvTime->tv_sec, ptvTime->tv_usec);
            break;
        }

        retryCnt++;
        usleep(50 * 1000);
	}

    if (retryCnt >= retryMaxNum) {
        printf("ntp retryCnt (%d) more than retryMaxNum (%d)\n", retryCnt, retryMaxNum);
        ret = -1;
        goto close_fd;
    }
    
close_fd:
    close(sockfd);
    return ret;
}

/**
 * NTP客户端同步时间,阻塞方式
 *
 * @param 无
 *
 * @retval 0 成功
 * @retval -1 失败
 *
 * @attention 无
 */
int NtpClientSyncTime(void)
{
    int i = 0;
    int ret = 0;
    struct timeval tvTime;
    int serverNum = 4;
    char *arryServerAddr[4] = {"ntp.aliyun.com", "ntp.ntsc.ac.cn", "jp.pool.ntp.org", "cn.pool.ntp.org"}; 

    for (i = 0; i < serverNum; i++)
    {
        memset(&tvTime, 0, sizeof(tvTime));
        ret = ntp_sync_time(arryServerAddr[i], &tvTime);
        if (ret < 0) {
            printf("ntp_sync_time %s failed\n", arryServerAddr[i]);
            usleep(50 * 1000);
            continue;
        } else {
            gs_bNtpOk = 1;
            print_ntp_time(arryServerAddr[i], tvTime);
            set_system_time(tvTime);
            return 0;
        }
    }
    
    if (i >= serverNum) {
        printf("ntp sync time failed !!!\n");
        return -1;
    }

    return 0;
}

void *tskNtpClient(void *args)
{
    pthread_detach(pthread_self());

    int ret = 0;
    
    while (1)
    {
        ret = NtpClientSyncTime();
        if (ret < 0) {
            printf("NtpClientSyncTime failed!\n");
            usleep(50 * 1000);
            continue;
        } else {
            printf("NtpClientSyncTime ok!\n");
            break;
        }
    }
    
	pthread_exit(0);
}

/**
 * NTP客户端同步时间, 非阻塞方式
 *
 * @param 无
 *
 * @retval 0 成功
 * @retval -1 失败
 *
 * @attention 无
 */
int NtpClientStart(void)
{
    int ret = 0;
    pthread_t ntpPid;

    ret = pthread_create(&ntpPid, NULL, &tskNtpClient, NULL);
    if (ret < 0) {
        printf("pthread_create tskNtpClient failed\n");
        return -1;
    }

    return 0;
}

/**
 * 获取NTP同步状态
 *
 * @param 无
 *
 * @retval 0 失败
 * @retval 1 成功
 *
 * @attention 无
 */
int NtpClientGetState(void)
{
    return gs_bNtpOk;
}


#if 0
int main(int argc, const char *argv[])
{
#if 1
	NtpClientStart();
	getchar();
#else
	if (argc < 2) {
		printf("Usage:\n");
		printf("    %s [server]\n", argv[0]);
		return -1;
	}

	int ret = 0;
	char ip[16] = "";
	struct timeval tvTime;

	domain_to_ip((char *)argv[1], ip);
	printf("ip ==== %s\n", ip);

	memset(&tvTime, 0, sizeof(tvTime));
	ret = ntp_sync_time((char *)argv[1],  &tvTime);
	if (ret < 0) {
		printf("ntp_sync_time failed\n");
		return -1;
	}

	print_ntp_time((char *)argv[1], tvTime);
#endif

	return 0;
}
#endif

